﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nine.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;

// Make internal types and members visible to the serialization assembly.
[assembly: InternalsVisibleTo("UnitTest.Serialization")]

namespace UnitTest
{
    [TestClass]
    public partial class TestCases
    {
        #region Common
        static BinarySerializer serializer = new BinarySerializer();
        static T Clone<T>(T value)
        {
            var stream = new MemoryStream();
            serializer.Save(stream, value);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)serializer.Load(stream);
        }

        static T Clone<T>(T value, T existingInstance)
        {
            var stream = new MemoryStream();
            serializer.Save(stream, value);
            stream.Seek(0, SeekOrigin.Begin);
            var result = (T)serializer.Load(stream, existingInstance, null);
            Assert.ReferenceEquals(existingInstance, result);
            return result;
        }

        static void Clone<T>(T value, Action<T> instance) where T : new()
        {
            var stream = new MemoryStream();
            serializer.Save(stream, value);

            // New instance
            stream.Seek(0, SeekOrigin.Begin);
            instance((T)serializer.Load(stream));

            // Existing instance
            stream.Seek(0, SeekOrigin.Begin);
            var existing = new T();
            Assert.ReferenceEquals(existing, serializer.Load(stream, existing, null));
            instance(existing);
        }
        #endregion

        #region PrimitiveTypes
        [BinarySerializable]
        public class Test0
        {
            public byte A = 1;
            public char B = 'C';
            public short C = short.MinValue;
            public ushort D = ushort.MaxValue;
            public int E = int.MinValue;
            public uint F = uint.MaxValue;
            public long G = long.MaxValue;
            public ulong H = ulong.MinValue;
            public TimeSpan I = TimeSpan.MaxValue;
            public DateTime J = DateTime.MaxValue;
        }

        [TestMethod]
        public void PrimitiveTypes()
        {
            var obj = new Test0();

            Clone(obj, cloned =>
            {
                Assert.AreEqual(obj.A, cloned.A);
                Assert.AreEqual(obj.B, cloned.B);
                Assert.AreEqual(obj.C, cloned.C);
                Assert.AreEqual(obj.D, cloned.D);
                Assert.AreEqual(obj.E, cloned.E);
                Assert.AreEqual(obj.F, cloned.F);
                Assert.AreEqual(obj.G, cloned.G);
                Assert.AreEqual(obj.H, cloned.H);
                Assert.AreEqual(obj.I, cloned.I);
                Assert.AreEqual(obj.J, cloned.J);
            });
        }
        #endregion

        #region GetOnlyCollection
        [BinarySerializable]
        public class Test1
        {
            public IList<int> Values { get { return values; } }
            private IList<int> values = new List<int>();

            public IDictionary<int, string> Map { get; private set; }
            public Test1() { Map = new Dictionary<int, string>(); }
        }

        [TestMethod]
        public void GetOnlyCollection()
        {
            var obj = new Test1();
            obj.Values.Add(1);
            obj.Values.Add(2);
            obj.Map.Add(1, "aa");
            obj.Map.Add(2, "bb");

            Clone(obj, cloned =>
            {
                Assert.IsTrue(obj.Values.SequenceEqual(cloned.Values));
                Assert.IsTrue(obj.Map.SequenceEqual(cloned.Map));
            });
        }
        #endregion

        #region Polymorphism
        [BinarySerializable]
        public class Test2
        {
            public TimeSpan Time;
            public IList<object> Children { get; private set; }
            public Test2() { Children = new List<object>(); }
        }

        [TestMethod]
        public void Polymorphism()
        {
            var obj = new Test2();
            obj.Time = TimeSpan.FromSeconds(123);
            obj.Children.Add(new Test1());
            obj.Children.Add(new Test2());

            Clone(obj, cloned =>
            {
                Assert.AreEqual(obj.Time, cloned.Time);
                Assert.IsInstanceOfType(obj.Children.First(), typeof(Test1));
                Assert.IsInstanceOfType(obj.Children.Last(), typeof(Test2));
            });
        }
        #endregion

        #region ArrayOfArray
        [BinarySerializable]
        public class Test3
        {
            public int[][] IntArrays;
            public object[][] ObjectArrays;
        }

        [TestMethod]
        public void ArrayOfArray()
        {
            var obj = new Test3();
            obj.IntArrays = Enumerable.Range(1, 4).Select(x => new int[x]).ToArray();
            obj.ObjectArrays = Enumerable.Range(0, 1).Select(x => new object[x]).ToArray();

            Clone(obj, cloned =>
            {
                for (int i = 0; i < obj.IntArrays.Length; i++)
                    Assert.IsTrue(obj.IntArrays[i].SequenceEqual(cloned.IntArrays[i]));
                Assert.IsTrue(obj.ObjectArrays[0].SequenceEqual(cloned.ObjectArrays[0]));
            });
        }
        #endregion

        #region InternalMembers
        [BinarySerializable]
        internal class Test4
        {
            [BinarySerializable]
            internal string A;
            [BinarySerializable]
            internal int B { get; set; }
            [BinarySerializable]
            internal ICollection<int> C = new List<int>();
            [BinarySerializable]
            internal HashSet<int> D = new HashSet<int>();
        }

        [TestMethod]
        public void InternalMembers()
        {
            var obj = new Test4();
            obj.A = "asdf";
            obj.B = 100;
            obj.C.Add(12);
            obj.D.Add(1);
            obj.D.Add(23);

            Clone(obj, cloned =>
            {
                Assert.AreEqual(obj.A, cloned.A);
                Assert.AreEqual(obj.B, cloned.B);
                Assert.IsTrue(obj.C.SequenceEqual(cloned.C));
                Assert.IsTrue(obj.D.SequenceEqual(cloned.D));
            });
        }
        #endregion

        #region Nullable
        [BinarySerializable]
        public class Test5
        {
            public int? A;
            public int? B;
        }

        [TestMethod]
        public void Nullable()
        {
            var obj = new Test5();
            obj.A = -23423;

            Clone(obj, cloned =>
            {
                Assert.AreEqual(obj.A, cloned.A);
                Assert.AreEqual(obj.B, cloned.B);
            });
        }
        #endregion

        #region CollectionItemReuse
        [TestMethod]
        public void CollectionItemReuse()
        {
            var root = new Test2();
            var node1 = new Test2 { Time = TimeSpan.FromSeconds(123) };
            var node2 = new Test2 { Time = TimeSpan.FromSeconds(1234) };
            root.Children.Add(node1);
            node1.Children.Add(node2);

            var clonedRoot = new Test2();
            var clonedNode1 = new Test2();
            var clonedNode2 = new Test2();
            clonedRoot.Children.Add(clonedNode1);
            clonedNode1.Children.Add(clonedNode2);

            clonedRoot = Clone(root, clonedRoot);

            Assert.ReferenceEquals(clonedRoot.Children[0], clonedNode1);
            Assert.ReferenceEquals(clonedNode1.Children[0], clonedNode2);
            Assert.AreEqual(node1.Time, clonedNode1.Time);
            Assert.AreEqual(node2.Time, clonedNode2.Time);
        }
        #endregion

        #region DictionaryItemReuse
        [BinarySerializable]
        public class Test6
        {
            public TimeSpan Time;
            public IDictionary<object, object> Children { get { return children; } }
            private Dictionary<Object, Object> children = new Dictionary<object, object>();
        }

        [TestMethod]
        public void DictionaryItemReuse()
        {
            var root = new Test6();
            var key1 = new Test6 { Time = TimeSpan.FromSeconds(123) };
            var node1 = new Test6 { Time = TimeSpan.FromSeconds(1234) };
            var key2 = new Test6 { Time = TimeSpan.FromSeconds(12345) };
            var node2 = new Test6 { Time = TimeSpan.FromSeconds(123456) };
            root.Children.Add(key1, node1);
            node1.Children.Add(key2, node2);

            var clonedRoot = new Test6();
            var clonedKey1 = new Test6();
            var clonedKey2 = new Test6();
            var clonedNode1 = new Test6();
            var clonedNode2 = new Test6();
            clonedRoot.Children.Add(clonedKey1, clonedNode1);
            clonedNode1.Children.Add(clonedKey2, clonedNode2);

            clonedRoot = Clone(root, clonedRoot);

            Assert.ReferenceEquals(clonedRoot.Children.First().Key, clonedKey1);
            Assert.ReferenceEquals(clonedRoot.Children.First().Value, clonedNode1);
            Assert.ReferenceEquals(clonedNode1.Children.First().Key, clonedKey2);
            Assert.ReferenceEquals(clonedNode1.Children.First().Value, clonedNode2);
            Assert.AreEqual(node1.Time, clonedNode1.Time);
            Assert.AreEqual(node2.Time, clonedNode2.Time);
            Assert.AreEqual(key1.Time, clonedKey1.Time);
            Assert.AreEqual(key2.Time, clonedKey2.Time);
        }
        #endregion

        #region ArrayItemReuse
        [BinarySerializable]
        public class Test7
        {
            public TimeSpan Time;
            public object[] Children;
        }

        [TestMethod]
        public void ArrayItemReuse()
        {
            var root = new Test7();
            var node1 = new Test7 { Time = TimeSpan.FromSeconds(1234) };
            var node2 = new Test7 { Time = TimeSpan.FromSeconds(123456) };
            var node3 = new Test7 { Time = TimeSpan.FromSeconds(12) };
            root.Children = new object[] { node1 };
            node1.Children = new object[] { node2, node3 };

            var clonedRoot = new Test7();
            var clonedNode1 = new Test7();
            var clonedNode2 = new Test7();
            var clonedNode3 = new Test7();
            clonedRoot.Children = new object[] { clonedNode1 };
            clonedNode1.Children = new object[] { clonedNode2, clonedNode3 };

            clonedRoot = Clone(root, clonedRoot);

            Assert.ReferenceEquals(clonedRoot.Children[0], node1);
            Assert.ReferenceEquals(clonedNode1.Children[0], clonedNode2);
            Assert.ReferenceEquals(clonedNode1.Children[1], clonedNode3);
            Assert.AreEqual(node1.Time, clonedNode1.Time);
            Assert.AreEqual(node2.Time, clonedNode2.Time);
            Assert.AreEqual(node3.Time, clonedNode3.Time);
        }
        #endregion

        #region EnumValues
        public enum TrueFalse : sbyte
        {
            True = sbyte.MinValue,
            False = sbyte.MaxValue,
        }

        public enum LargeEnum : int
        {
            Min = int.MinValue,
            Max = int.MaxValue,
        }

        [BinarySerializable]
        public class Test8
        {
            public TrueFalse A = TrueFalse.True;
            public TrueFalse B = TrueFalse.False;
            public LargeEnum C = LargeEnum.Min;
            public LargeEnum D = LargeEnum.Max;
        }

        [TestMethod]
        public void EnumValues()
        {
            var obj = new Test8();
            obj.A = TrueFalse.False;
            obj.B = TrueFalse.True;
            obj.C = LargeEnum.Max;
            obj.D = LargeEnum.Min;

            Clone(obj, cloned =>
            {
                Assert.AreEqual(obj.A, cloned.A);
                Assert.AreEqual(obj.B, cloned.B);
                Assert.AreEqual(obj.C, cloned.C);
                Assert.AreEqual(obj.D, cloned.D);
            });
        }
        #endregion
    }
}
